/**
 * @author rdru
 * INV: 
 */
package server;
import static server.ServerMonitor.docAdd;
import static server.ServerMonitor.docClose;
import static server.ServerMonitor.docOpen;
import static server.ServerMonitor.docRemove;
import static server.ServerMonitor.lockGrant;
import static server.ServerMonitor.lockRelease;
import static server.ServerMonitor.lockRequest;
import static server.ServerMonitor.logDocClose;
import static server.ServerMonitor.logDocOpen;
import static server.ServerMonitor.logLockDeny;
import static server.ServerMonitor.logLockGrant;
import static server.ServerMonitor.logLockRelease;
import static server.ServerMonitor.logLockRequest;
import static server.ServerMonitor.logRecieved;
import static server.ServerMonitor.logUserLogin;
import static server.ServerMonitor.logUserLogout;
import static server.ServerMonitor.received;
import static server.ServerMonitor.serverLogAdd;
import static server.ServerMonitor.userLogin;
import static server.ServerMonitor.userLogout;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.swing.JOptionPane;

import abstractions.ClientServices;
import abstractions.LoggedUser;
import abstractions.AtomId;
import abstractions.CommandC;
import abstractions.Doc;
import abstractions.OpenDoc;
import abstractions.OpenDocInterface;
import abstractions.ServerServices;
import abstractions.User;
import server.ServerMonitor;

public class Server implements ServerServices{
	// *************** Server windows
	static ServerW			serverW;
	static ServerSetup		setup;
	static ServerMonitor	monitor;
	
	// Names/URL of knwn Servers: this should eventualy be loaded from a file
	private final String[]		serverNames		= {"Local"};
	private final String[]		serverURL		= {"http://localhost"};
	private final static String	serverPath		= "C:\\Users\\Shared\\EMU_Server\\";	
	private final String		EMUUserFile		= serverPath + "userList";
	private final String		sharedDirectory	= serverPath + "shared";
	private final String		admin			= "admin";

	// user and files known to the Server
	static HashMap<String, Doc>				regDocs;				// <docPath, doc>
	static HashMap<String, User>			regUsers;				// <logname, User>
	static TreeSet<String>					regUsersName;			// <logname>
	static HashMap<String, String>			passwords;				// passwords of reg users <logname, password>

	// dynamic data (currently logged clients and open documents)
	static HashMap<Integer, ActiveDoc>		activeDocs;				// docId, Doc
	public static HashMap<String, Integer>	openDocs;				// docName, docId
	static HashMap<Integer, LoggedUser>		loggedUsers;			// userId, User
	static HashMap<Integer, String>			clients;				// clientId, IP
	
	// id manager (for now the ids are generated sequentially...)
	int								myServerId		= 0;
	public static int				nextUserId		= 0;
	public static int				nextClientId	= 0;
	public static int				nextDocId		= 0;
	
	// server monitor log
	public static StringBuilder		fullLog			= new StringBuilder();
	
	Server() {

		// Creates main structures.
		regDocs = new HashMap<String, Doc>();
		regUsers = new HashMap<String, User>();
		regUsersName = new TreeSet<String>();
		passwords = new HashMap<String, String>();
		activeDocs = new HashMap<Integer, ActiveDoc>();
		openDocs = new HashMap<String, Integer>();
		loggedUsers = new HashMap<Integer, LoggedUser>();
		clients = new HashMap<Integer, String>();
		
		// Reading the password.txt file to retrieve the admin password.
		// Also populates the structures.
		try {
			FileInputStream fstream = new FileInputStream(serverPath + "password.txt");
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String line;
			while ((line = br.readLine()) != null)   {
				String[] s = line.split("\t");
				if (!(s[0].equals("admin"))) {
					passwords.put(s[0], s[1]);
					regUsers.put(s[0], new User(s[0], s[2], s[3]));
					regUsersName.add(s[0]);
				}
				System.out.println(s[0]);
				System.out.println(s[1]);
				System.out.println (s[0] + " password: " + s[1]);
				passwords.put(s[0], s[1]);
				if (!passwords.get(s[0]).equals(s[1]))
					System.out.println("error passwords");
			}
			in.close();
		} catch (Exception e){
			System.err.println("Error: " + e.getMessage());
		}
		
		// Asks for the admin password.
		// If the given password is not correct, the program will shut down.
		String passwd = JOptionPane.showInputDialog(null, "Enter the admin password: ", "EMU Server - Admin login", 1);
		if (!passwords.get(admin).equals(passwd)) {
			// show invalid authentication dialog
			JOptionPane.showMessageDialog(null, "Wrong password! Program shutting down.");
			System.exit(0);
		}
		
		// Initiating the Server windows.
		serverW	= new ServerW();
		setup	= new ServerSetup();
		monitor = new ServerMonitor();
		
		
		// reads the file list from file system and inserts than into docsList ...
		// at boot, the server adds all files from the shared file directory.

		
		// start all service threads
		
		// Shows Server Window
		serverW.setVisible(true);
		
	}
	
	
	// *************** local services
	
	/* By Rafael Tamashiro
	 * This function writes the new user name (and all the information related) to the
	 * file password.txt, which is the one responsible for keeping track of all users,
	 * so the hashmaps can be loaded again after the program is reopened.
	 */
	static void writePassword(String logname, String pass, String userName, String userEmail) {
		try {
			FileInputStream fstream = new FileInputStream(serverPath + "password.txt");
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			
			File newFile = new File(serverPath + "auxiliar.txt");
			boolean success = newFile.createNewFile();
			if (!success) System.out.println("Erro ao criar o arquivo." + "\n");
			
			FileWriter fstream2 = new FileWriter(serverPath + "auxiliar.txt");
			BufferedWriter bw = new BufferedWriter(fstream2);
			
			String line;
			while ((line = br.readLine()) != null) {
				bw.write(line + "\r\n");
			}
			in.close();
			File deleteFile = new File(serverPath + "password.txt");
			deleteFile.setWritable(true);
			success = (deleteFile).delete();
			if (!success) System.out.println("Erro ao remover o arquivo." + "\n");
			bw.write(logname + "\t" + pass + "\t" + userEmail + "\t" + userName + "\r\n");
			
			bw.close();
			
			File oldFile = new File(serverPath + "auxiliar.txt");
			oldFile.renameTo(new File (serverPath + "password.txt"));
			setup.refreshUsersList();
		}	catch (Exception e) {
			System.err.println("Error: " + e.getMessage());
		}
	}
	
	/* By Rafael Tamashiro
	 * This function is responsible for removing the desired logname
	 * from the users list in password.txt, then
	 * refreshing the frame, so the user can see the updated
	 * list of users in the setup window. The method refreshUsersList(); is located
	 * in the class ServerWindow.
	 */
	static void removePassword(String logname) {
		try {
			FileInputStream fstream = new FileInputStream(serverPath + "password.txt");
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			
			File newFile = new File(serverPath + "auxiliar.txt");
			boolean success = newFile.createNewFile();
			if (!success) System.out.println("Erro ao criar o arquivo." + "\n");
			
			FileWriter fstream2 = new FileWriter(serverPath + "auxiliar.txt");
			BufferedWriter bw = new BufferedWriter(fstream2);
			
			String line;
			while ((line = br.readLine()) != null) {
				String[] s = line.split("\t");
				if (!(s[0].equals(logname)))
						bw.write(line + "\r\n");
			}
			in.close();
			File deleteFile = new File(serverPath + "password.txt");
			deleteFile.setWritable(true);
			success = (deleteFile).delete();
			if (!success) System.out.println("Erro ao remover o arquivo." + "\n");
			
			bw.close();
			
			File oldFile = new File(serverPath + "auxiliar.txt");
			oldFile.renameTo(new File (serverPath + "password.txt"));
		}	catch (Exception e) {
			System.err.println("Error: " + e.getMessage());
		}
		setup.refreshUsersList();
	}

	// 
	// returns true if pair <Client/User> is valid and on the loggedUsers list,
	// false otherwise.	
	static LoggedUser validateClient(int clientId, int userId) {
		LoggedUser u;
		if (! validateClient(clientId)
		    || (u = loggedUsers.get(userId)) == null)
			return null;
		return u;
	}

	static boolean validateClient(int clientId) {
		return clients.containsKey(clientId);
	}

	// returns true if user is validated, false o.w.
	// Pre: logname is not null
	static boolean validateUser(String logname, String password) {
		return password.equals(passwords.get(logname));
	}

	static ActiveDoc openFile(Doc d) throws RemoteException {
		// open the doc file and read its contains into text...
		String text = "substitute by the doc text";
		
		if (false)			// substitute false by expression: can not open/read file
			return null;
		ActiveDoc ad = new ActiveDoc(d, text);		
		return ad;
	}

	static boolean saveCloseFile(int docId) {
		return true;
	}
	
	// addDoc: add a new document to regDocs.
	// returns true in case of success and false otherwise
	public boolean docAdd(int clientId, int userId, String docName, String docText) {
		//logDocAdd(userId, docName);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(docAdd.isSelected(), ": Failed validate clientId");
			return false;
		}
		// add doc to regDocs and create a new file for the new doc...
		return true;
	}

	public boolean docRemove(int clientId, int userId, String docName) {
		//logDocRemove(userId, docName);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(docRemove.isSelected(), " Failed validate clientId or userId!!");
			return false;
		}

		// do not know how to handle this yet ...
		return true;
	}

	static int registerClient(String IP) {
		clients.put(nextClientId++, IP);
		return nextClientId - 1;
	}

	static void removeClient(int clientId) {
		validateClient(clientId);
		clients.remove(clientId);
	}

	static void readPasswd() {
		
	}

	static void writePasswd() {
		
	}
	
	// adds a new user in the server regUsers list.
	static public void addUser(String logname, String password, String userName, String userEmail) {
			if (regUsers.containsKey(logname)) {
	            JOptionPane.showMessageDialog(monitor.getContentPane(),
	                    "Add User:\nLogname already in use.", "Add User", JOptionPane.ERROR_MESSAGE);
	            return;
			}

		// inserts the user
		regUsers.put(logname, new User(logname, userEmail, userName));
		passwords.put(logname, password);
		regUsersName.add(logname);
		
		// save the password file (to prevent corruption on a power failure)...
		writePassword(logname, password, userName, userEmail);
	}
	
	// remove a user from the user list.  Returns false on error, or true otherwise
	// Pre: user does not have an open session
	static public boolean removeUser(User u) {
		if (loggedUsers.containsKey(u)) {
            JOptionPane.showMessageDialog(monitor.getContentPane(),
                    "Remove User:\nUser is currently active, can not remove.", "Remove User", JOptionPane.ERROR_MESSAGE);
            return false;
		}
		regUsers.remove(u);
		passwords.remove(u.logname);
		regUsersName.remove(u.logname);
		
		removePassword(u.logname);
		return true;
	}
	
	// 
	public static boolean validateAdm() {
		// validadte adm...
        JOptionPane.showMessageDialog(monitor.getContentPane(),
                "Can not validate Admin user", "Admin Authentication", JOptionPane.ERROR_MESSAGE);
        return true;
	}
	
	// add user to the list of users editing a given doc
	public static void addUserToDoc(String client, int userId, String docName) {}
	
	// remove user from the list of users editing a given doc
	public void removeUserToDoc(String client, int userId, String docName) {}
	
	public static String userName(int userId) {
		String s = null;
		return s;
	}
	
	public static String docName(int docId) {
		String s = null;
		return s;
	}
	
	public static String[] userNames(int[] users) {
		String s[] = null;
		return s;
	}
	
	public static String[] docNames(int[] docs) {
		String[] s = null;
		return s;
	}
	
	
	// logs all 

	// **************** service provided to clients:
	
	// authenticate the given logname and register the user as loged on client c.
	// Returns the User data in case of success, otherwise returns null.
	public LoggedUser login(int clientId, String logname, String password) {
		logUserLogin(clientId, logname);
		if (! validateClient(clientId))
			return null;
		if (! validateUser(logname, password)) {
			serverLogAdd(userLogin.isSelected(), " Failed wrong logname/pasword!!");
			return null;
		}
		LoggedUser u = (LoggedUser) regUsers.get(logname);
		if (loggedUsers.containsValue(u)) {
			serverLogAdd(userLogin.isSelected(), " Failed already logged!!");
			return null;
		}
		loggedUsers.put(nextUserId++, u);
		return u;
	}
	
	// logout the user: release all locks and send any pending updates to other clients,
	// removes all associated docs from the docsList, 
	// remove the user from the regUsers and send to all clients editing some
	// of this user's docs a removeUserLocally.
	public boolean logout(int clientId, int userId) {
		logUserLogout(userId);
		LoggedUser u;
		if ((u = validateClient(clientId, userId)) == null) {
			serverLogAdd(userLogout.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		if (!u.edits.isEmpty()) {
			serverLogAdd(userLogout.isSelected(), " Failed needs to close open docs");
			return false;
		}
		loggedUsers.remove(userId);
		return true;
	}

		
	// remove a doc from the server managed documents docsList.
	public static boolean removeDocument(String client, int userId, String docName) {
		return true;
	}
	
	// add a doc from the server managed documents docsList.
	public static boolean AddDocument(String client, int userId, String docName, String docText) { 
		return false;
	}
	// openDoc: opens an existing document stored in the server.
	// For now the server handles all documents in a single directory and
	// all users have the same access rights...
	// docPath (for now) is just the file's name.
	// Returns the docId of the opened document, or -1 in case of error ...
	public static OpenDoc docOpen(int clientId, int userId, String docName, String docPath) throws IOException {
		logDocOpen(userId, docName);
		LoggedUser u;
		if ((u = validateClient(clientId, userId)) == null) {
			serverLogAdd(docOpen.isSelected(), " Failed validate clientId/userId");
			return null;
		}
		
		// 
		Doc d = regDocs.get(docPath+docName);
		if (d == null) {
			serverLogAdd(docOpen.isSelected(), " Failed doc does not exist!!");
			return null;
		}
		 
		// if doc is not open, open it
		ActiveDoc ad;
		int docId = openDocs.get(docPath+docName);
		if (docId < 0) {
			ad = openFile(d);
			if (ad == null) {
				serverLogAdd(docOpen.isSelected(), " Failed can not open doc file");
				return null;
			}
		} else
			ad = activeDocs.get(docId); // opens and add to activeDocs
		
		// add user to editedBy
		ad.editedBy.add(userId);
		return ad;
	}

	public void docClose(int clientId, int userId, int docId) {
		logDocClose(userId, docId);
		LoggedUser u;
		if ((u = validateClient(clientId, userId)) == null) {
			serverLogAdd(docClose.isSelected(), " Failed validate clientId/userId");
			return;
		}
		ActiveDoc d =  activeDocs.get(docId);
		d.upr = d.histOld.size() + d.histNew.size();	// updates received
		d.ups = d.histOld.size();						// updates sent
		d.n = 0;										// total # commands received
		for (LinkedList<CommandC> c: d.histOld)
			d.n += c.size();
		d.n += d.histNew.size();
		if ((d = activeDocs.get(docId)) == null) {
			serverLogAdd(docClose.isSelected(), " Failed doc does not exist!!");
			return;
		}
		d.editedBy.remove(docId);
		u.edits.remove(docId);
		if (d.editedBy.isEmpty()) {
			// update and save doc to file...
			activeDocs.remove(docId);
		}
		serverLogAdd(docClose.isSelected(), ", received: " + d.upr
		             + " sent: " + d.ups + " comands:" + d.n);
	}


	public boolean lockRequest(int clientId, int userId, int docId, AtomId aId) {
		logLockRequest(userId, docId, aId);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(lockRequest.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		ActiveDoc d = activeDocs.get(docId);
		if (d.locked.containsKey(aId)) {
			logLockDeny(userId, docId, aId);
			return false;
		} else {
			logLockGrant(userId, docId, aId);
			d.locked.put(aId, userId);
			return true;
		}
	}

	public boolean lockRelease(int clientId, int userId, int docId, AtomId aId) {
		logLockRelease(userId, docId, aId);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(lockRelease.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		ActiveDoc d = activeDocs.get(docId);
		int uId = d.locked.get(aId);
		if (uId == -1 || uId != userId) {
			serverLogAdd(lockGrant.isSelected(), " Failed non-existent lock or locked to other user");
			return false;
		}
		d.locked.remove(aId);
		return true;
	}

	public boolean updateDoc(int clientId, int userId, int docId, LinkedList<CommandC> cmds) {
		logRecieved(userId, docId, cmds.size());
		LoggedUser u = validateClient(clientId, userId);
		if (u == null) {
			serverLogAdd(received.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		ActiveDoc d = u.edits.get(docId);
		if (d != null) {
			d.histNew.addAll(cmds);
			return true;
		} else {
			serverLogAdd(received.isSelected(), " Failed: user does not have this docId open");
			return false;
		}
	}

	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//Schedule a job for the event-dispatching thread:
		//creating and showing this application's GUI.
		javax.swing.SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				// connect with the server

				// creates the thread pool for server edits
				
				// instatiate a new server
				new Server();
				
				// init the first EMU Window
				serverW.setVisible(true);
			}
		});
	}


	@Override
	public int login(ClientServices cl, int clientId, String logname,
			String password) throws RemoteException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public OpenDocInterface docOpen(int clientId, int userId, String docPath)
			throws RemoteException {
		// TODO Auto-generated method stub
		return null;
	}
}